﻿using System;
using System.IO;
using System.Net;
using System.Text;

namespace FirstFloor.Documents.IO
{
    /// <summary>
    /// Encapsulates a package part request over the web.
    /// </summary>
    public class WebPackagePartRequest2
        : IPackagePartRequest
    {
        class AsyncState
        {
            public AsyncCallback Callback { get; set; }
            public object State { get; set; }
        }

        private WebPackageReader2 packageReader;
        private Uri partUri;
        private HttpWebRequest request;

        /// <summary>
        /// Initializes a new instance of the <see cref="WebPackagePartRequest2"/> class.
        /// </summary>
        /// <param name="packageReader">The package reader.</param>
        /// <param name="partUri">The part URI.</param>
        internal WebPackagePartRequest2(WebPackageReader2 packageReader, Uri partUri)
        {
            this.packageReader = packageReader;
            this.partUri = partUri;

            var webUri = packageReader.ResolvePartUri(partUri);
            this.request = (HttpWebRequest)WebRequest.Create(webUri);
        }

        /// <summary>
        /// Aborts the pending request.
        /// </summary>
        public void Abort()
        {
            this.request.Abort();
        }

        /// <summary>
        /// Begins an asynchronous load of specified part.
        /// </summary>
        /// <param name="callback">The callback.</param>
        /// <param name="state">The state.</param>
        /// <returns>
        /// An <see cref="System.IAsyncResult"/> that references the asynchronous request for a response.
        /// </returns>
        public IAsyncResult BeginLoadPart(AsyncCallback callback, object state)
        {
            // POST list of previously request part uris
            this.request.Method = "POST";
            return this.request.BeginGetRequestStream(OnGetRequestStream, new AsyncState { Callback = callback, State = state });
        }

        private void OnGetRequestStream(IAsyncResult result)
        {
            using (var stream = this.request.EndGetRequestStream(result)) {
                // write each part uri's on seperate line
                var postBody = string.Join("\r\n", this.packageReader.GetCachedPartUris());

                var data = Encoding.UTF8.GetBytes(postBody);

                stream.Write(data, 0, data.Length);
            }
            var state = (AsyncState)result.AsyncState;

            // start getting the response
            this.request.BeginGetResponse(state.Callback, state.State);
        }

        /// <summary>
        /// Ends an asynchronous load part operation. Null is returned if the specified part could not be found.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns>The part content stream.</returns>
        public Stream EndLoadPart(IAsyncResult result)
        {
            try {
                WebResponse response = request.EndGetResponse(result);
                var stream = response.GetResponseStream();

                // if response is a bundled page response, store ZIP and return actual page stream
                if (ZipFile.IsZipFile(stream)) {
                    var zipFile = this.packageReader.StoreZip(stream);

                    return zipFile.GetFileStream(this.partUri);
                }

                // not a zip, return as-is
                stream.Seek(0, SeekOrigin.Begin);
                return stream;
            }
            catch (Exception e) {
                // return null in case of 404 Not Found
                var webException = e as WebException;
                if (webException != null) {
                    if (webException.Status == WebExceptionStatus.RequestCanceled) {
                        return null;
                    }
                    var httpWebResponse = webException.Response as HttpWebResponse;
                    if (httpWebResponse != null && httpWebResponse.StatusCode == HttpStatusCode.NotFound) {
                        return null;
                    }
                }
                throw;
            }
        }
    }
}
